AWS FargateでFireLensを使って同じログを3箇所に送ってみた
AWS Fargateのログ管理でFireLens(Fluent Bit)を利用して複数のログ保存先を設定してみる。
- アプリケーションが標準出力に出力したログをCloudWatch Logsと、S3バケットに保存してみます。
- Firehose経由せずS3バケットへ直接送るFluent Bitのプラグインもあったのでついでに試しました。
- Fluent Bitの設定ファイルはFluent BitのDockerイメージに同梱しました。
検証環境
項目 | バージョン |
---|---|
aws-for-fluent-bit | 2.10.1 |
Fluent Bit | 1.6.10 |
Fargate platform | 1.4.0 |
Fluent BitとFluentd
Firelensで起動するログルーティングのコンテナは同じタスク内にサイドカーとして起動します。 軽量なコンテナの方が嬉しいのでFluent Bitを採用しました。
参考: Fluent Bit による集中コンテナロギング | Amazon Web Services ブログ
設定ファイル込みのイメージ作成
同じログ内容を合計3箇所の出力先へ送るシンプルなFluent Bitの設定を作るところからはじめます。Fluent Bitの設定次第で特定のログであればCloudWatch Logsへ、それ以外はS3バケットへ送信も可能です。Fluent Bitのドキュメントまで読みきれなかったので細かい設定は断念。
Fluent Bitの設定ファイル作成
各プラグインの設定はFluent Bitドキュメントを参考に出力先を設定しました。
S3へ直接する保存する場合はドキュメント内で注意事項として触れられています。Fargateのような永続ストレージがない実行環境でFluent Bitを起動する場合、Fluent Bitのコンテナが停止するとログを消失する可能性があります。高頻度でログを送信するのを推奨されています。とあるため、従来どおりKinesis Data Firehoseを経由でS3へ送信する方が安全な構成と言えるでしょう。ただ、S3用のプラグインを試したいので設定します。
[OUTPUT] Name cloudwatch Match * region us-east-1 log_group_name /ecs/logs/fluentbit-dev-ecs-group log_stream_name from-fluentbit auto_create_group true [OUTPUT] Name firehose Match * region us-east-1 delivery_stream fluentbit-dev-delivery-stream [OUTPUT] Name s3 Match * region us-east-1 bucket fluentbit-dev-directs3 total_file_size 1M upload_timeout 1m
参考:
- Amazon CloudWatch - Fluent Bit: Official Manual
- Amazon Kinesis Data Firehose - Fluent Bit: Official Manual
- Amazon S3 - Fluent Bit: Official Manual
2021/9/6追記プラグイン指定方法は以下のリンクもご確認ください
DockerfileとECRへイメージアップロード
ECS EC2の場合、S3にFluent Bitの設定ファイルを置いておけば設定ファイルを読み込んでくれる機能がサポートされています。しかし、Fargateはその機能がサポートされていません。そのため、Fluent Bitのコンテナ内に設定ファイルを入れておきます。
AWS Fargate でホストされるタスクは、file 設定ファイルタイプのみをサポートします。
FireLens 設定を使用するタスク定義の作成 - Amazon ECS
とは言え、Fluent Bit設定のファイルをイメージ内にコピーするだけです。Fluent Bitコンテナの設定ファイル読み込み指定はECSのタスク定義の設定で行います。ベースのイメージはAmazonが配布しているAWS用のプラグインが同梱されたFluent Bitのイメージを利用しています。
FROM amazon/aws-for-fluent-bit:2.10.1 COPY ./extra.conf /fluent-bit/etc/extra.conf
設定ファイル込みのイメージをECRへプッシュします。FireLensで起動するサイドカーコンテナの準備完了です。
docker build -t fluentbit-dev-my-firelens . docker tag fluentbit-dev-my-firelens:latest [AccountID].dkr.ecr.us-east-1.amazonaws.com/fluentbit-dev-my-firelens:latest aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin [AccountID].dkr.ecr.us-east-1.amazonaws.com docker push [AccountID].dkr.ecr.us-east-1.amazonaws.com/fluentbit-dev-my-firelens:latest
参考:
FireLens有効化とタスク定義の編集
Fargateの構築については省略します。
FireLens有効
FireLensの統合を有効にチェックを入れます。イメージはECRにアップした設定ファイル込みのFluent Bitのイメージを指定します。
有効化するとlog_routerコンテナが追加されます。
タスク定義の編集
必要なログ設定箇所がドキュメントななめ読みだと頭に入ってこなかったのでまとめると私の理解ではこうなりました。
- アプリコンテナ(キャプチャの例だとfluentbit-dev-container)のログドライバーはawsfirelensを指定
- FireLensのFluent Bitコンテナ(log_router)のログドライバーはawslogsを指定
- Fluent Bitのトラブル対応のためCloudWatch Logs(awslogs)にログを送ります
- logConfigurationと似ているfirelensConfigurationの項目に設定を追加します
アプリコンテナ設定
awslogsからawsfirelensに変更します。
変更後の該当箇所。optionsがnullか、optionsの項目が存在しないことを確認しましょう。管理コンソールからの設定だとデフォルトの設定値が残存していることがあります。
"logConfiguration": { "logDriver": "awsfirelens", "secretOptions": null, "options": null },
optionsに空欄があったときのエラー内容
FireLnesコンテナ設定
awslogsを指定して、CloudWatch Logsのロググループなど設定を追加します。
変更後の該当箇所
"logConfiguration": { "logDriver": "awslogs", "secretOptions": null, "options": { "awslogs-group": "/ecs/logs/fluentbit-dev-ecs-group", "awslogs-region": "us-east-1", "awslogs-stream-prefix": "from-log-router" } },
Fluent Bitの設定ファイルの指定はタスク定義のJSONファイルを直接編集します。
firelensConfigurationの項目を探します。
"firelensConfiguration": { "type": "fluentbit" },
変更後の該当箇所
"type": "fluentbit"
末尾の,
の付け忘れに注意options
でイメージに格納した自前の設定ファイルのパスを指定
"firelensConfiguration": { "type": "fluentbit", "options": { "config-file-type": "file", "config-file-value": "/fluent-bit/etc/extra.conf" } },
検証時のタスク定義全文
設定箇所の確認にお使いください。
折りたたみ
{ "ipcMode": null, "executionRoleArn": "arn:aws:iam::[AccountID]:role/fluentbit-dev-ECSTaskExecutionRolePolicy", "containerDefinitions": [ { "dnsSearchDomains": [], "environmentFiles": null, "logConfiguration": { "logDriver": "awsfirelens", "secretOptions": null, "options": null }, "entryPoint": [], "portMappings": [ { "hostPort": 80, "protocol": "tcp", "containerPort": 80 } ], "command": [], "linuxParameters": null, "cpu": 0, "environment": [], "resourceRequirements": null, "ulimits": null, "dnsServers": [], "mountPoints": [], "workingDirectory": null, "secrets": null, "dockerSecurityOptions": [], "memory": null, "memoryReservation": null, "volumesFrom": [], "stopTimeout": null, "image": "[AccountID].dkr.ecr.us-east-1.amazonaws.com/fluentbit-dev-sample-server:latest", "startTimeout": null, "firelensConfiguration": null, "dependsOn": null, "disableNetworking": null, "interactive": null, "healthCheck": null, "essential": true, "links": [], "hostname": null, "extraHosts": null, "pseudoTerminal": null, "user": null, "readonlyRootFilesystem": null, "dockerLabels": null, "systemControls": [], "privileged": null, "name": "fluentbit-dev-container" }, { "dnsSearchDomains": null, "environmentFiles": null, "logConfiguration": { "logDriver": "awslogs", "secretOptions": null, "options": { "awslogs-group": "/ecs/logs/fluentbit-dev-ecs-group", "awslogs-region": "us-east-1", "awslogs-stream-prefix": "from-log-router" } }, "entryPoint": null, "portMappings": [], "command": null, "linuxParameters": null, "cpu": 0, "environment": [], "resourceRequirements": null, "ulimits": null, "dnsServers": null, "mountPoints": [], "workingDirectory": null, "secrets": null, "dockerSecurityOptions": null, "memory": null, "memoryReservation": null, "volumesFrom": [], "stopTimeout": null, "image": "[AccountID].dkr.ecr.us-east-1.amazonaws.com/fluentbit-dev-my-firelens:latest", "startTimeout": null, "firelensConfiguration": { "type": "fluentbit", "options": { "config-file-type": "file", "config-file-value": "/fluent-bit/etc/extra.conf" } }, "dependsOn": null, "disableNetworking": null, "interactive": null, "healthCheck": null, "essential": true, "links": null, "hostname": null, "extraHosts": null, "pseudoTerminal": null, "user": "0", "readonlyRootFilesystem": null, "dockerLabels": null, "systemControls": null, "privileged": null, "name": "log_router" } ], "placementConstraints": [], "memory": "512", "taskRoleArn": "arn:aws:iam::[AccountID]:role/fluentbit-dev-ECSTaskExecutionRolePolicy", "compatibilities": [ "EC2", "FARGATE" ], "taskDefinitionArn": "arn:aws:ecs:us-east-1:[AccountID]:task-definition/fluentbit-dev-task:19", "family": "fluentbit-dev-task", "requiresAttributes": [ { "targetId": null, "targetType": null, "value": null, "name": "com.amazonaws.ecs.capability.logging-driver.awslogs" }, { "targetId": null, "targetType": null, "value": null, "name": "ecs.capability.execution-role-awslogs" }, { "targetId": null, "targetType": null, "value": null, "name": "com.amazonaws.ecs.capability.ecr-auth" }, { "targetId": null, "targetType": null, "value": null, "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19" }, { "targetId": null, "targetType": null, "value": null, "name": "ecs.capability.firelens.fluentbit" }, { "targetId": null, "targetType": null, "value": null, "name": "ecs.capability.firelens.options.config.file" }, { "targetId": null, "targetType": null, "value": null, "name": "com.amazonaws.ecs.capability.docker-remote-api.1.17" }, { "targetId": null, "targetType": null, "value": null, "name": "com.amazonaws.ecs.capability.logging-driver.awsfirelens" }, { "targetId": null, "targetType": null, "value": null, "name": "com.amazonaws.ecs.capability.task-iam-role" }, { "targetId": null, "targetType": null, "value": null, "name": "ecs.capability.execution-role-ecr-pull" }, { "targetId": null, "targetType": null, "value": null, "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18" }, { "targetId": null, "targetType": null, "value": null, "name": "ecs.capability.task-eni" } ], "pidMode": null, "requiresCompatibilities": [ "FARGATE" ], "networkMode": "awsvpc", "cpu": "256", "revision": 19, "status": "ACTIVE", "inferenceAccelerators": null, "proxyConfiguration": null, "volumes": [] }
タスクロールの追加
タスク実行ロールではなくタスクロールにログ送信先リソースへアクセスできるポリシーを追加します。
手抜きでフルアクセスのポリシーを追加しました。
手を抜かない例
FireLens サイドカー起動
サービス更新してタスク起動し直します。
log_router
コンテナも起動してきました。
ログ確認
FireLensからCloudWatch Logsへ
Fluent Bitコンテナ自身はawslogs指定でCloudWatch Logsへ保存設定しました。S3へアップロードしましたという大量のログを確認できました。高頻度で送信するようにしたためほぼこのログで埋まっています。Fluent Bitコンテナのトラブル調査はCloudwatch Logsからできます。
[2021/02/14 11:12:52] [ info] [output:s3:s3.2] Successfully uploaded object /fluent-bit-logs/fluentbit-dev-container-firelens-af27785192944632a76e3d1696c42686/2021/02/14/11/11/47-objectNj3AEbw5 [2021/02/14 11:14:22] [ info] [output:s3:s3.2] Successfully uploaded object /fluent-bit-logs/fluentbit-dev-container-firelens-af27785192944632a76e3d1696c42686/2021/02/14/11/13/17-objectyjr40CIG
アプリから各種保存先へ
アプリコンテナはELBの後ろにある文字列を返すだけのWEBサーバです。
$ curl http://fluentbit-dev-elb-586101502.us-east-1.elb.amazonaws.com/ "Hello, Abashiri"
まず、CloudWatch LogsにはELBのヘルスチェックのリクエストログを確認できました。Fluent BitからCloudWatch Logsへのルーティングは成功していました。
{ "container_id": "af27785192944632a76e3d1696c42686-3097909514", "container_name": "fluentbit-dev-container", "ecs_cluster": "fluentbit-dev-cluster", "ecs_task_arn": "arn:aws:ecs:us-east-1:[AccountID]:task/fluentbit-dev-cluster/af27785192944632a76e3d1696c42686", "ecs_task_definition": "fluentbit-dev-task:19", "log": "{\"time\":\"2021-02-14T11:18:44.214087349Z\",\"id\":\"\",\"remote_ip\":\"10.1.2.37\",\"host\":\"10.1.17.31\",\"method\":\"GET\",\"uri\":\"/\",\"user_agent\":\"ELB-HealthChecker/2.0\",\"status\":200,\"error\":\"\",\"latency\":40220,\"latency_human\":\"40.22µs\",\"bytes_in\":0,\"bytes_out\":18}", "source": "stdout" }
次にKinesis Data Firehose経由のS3バケット内の様子です。ダウンロードして確認すると同様にELBのヘルスチェックログを確認できました。
{"container_id":"af27785192944632a76e3d1696c42686-3097909514","container_name":"fluentbit-dev-container","ecs_cluster":"fluentbit-dev-cluster","ecs_task_arn":"arn:aws:ecs:us-east-1:[AccountID]:task/fluentbit-dev-cluster/af27785192944632a76e3d1696c42686","ecs_task_definition":"fluentbit-dev-task:19","log":"{\"time\":\"2021-02-14T11:30:14.682585288Z\",\"id\":\"\",\"remote_ip\":\"10.1.2.37\",\"host\":\"10.1.17.31\",\"method\":\"GET\",\"uri\":\"/\",\"user_agent\":\"ELB-HealthChecker/2.0\",\"status\":200,\"error\":\"\",\"latency\":51727,\"latency_human\":\"51.727µs\",\"bytes_in\":0,\"bytes_out\":18}","source":"stdout"} {"container_id":"af27785192944632a76e3d1696c42686-3097909514","container_name":"fluentbit-dev-container","ecs_cluster":"fluentbit-dev-cluster","ecs_task_arn":"arn:aws:ecs:us-east-1:[AccountID]:task/fluentbit-dev-cluster/af27785192944632a76e3d1696c42686","ecs_task_definition":"fluentbit-dev-task:19","log":"{\"time\":\"2021-02-14T11:30:44.408751519Z\",\"id\":\"\",\"remote_ip\":\"10.1.1.178\",\"host\":\"10.1.17.31\",\"method\":\"GET\",\"uri\":\"/\",\"user_agent\":\"ELB-HealthChecker/2.0\",\"status\":200,\"error\":\"\",\"latency\":37581,\"latency_human\":\"37.581µs\",\"bytes_in\":0,\"bytes_out\":18}","source":"stdout"}
最後は直接S3バケットにログを送った様子です。オブジェクトまでのパスが深いのと、ログフォーマットが今まで多少異なっていました。
{"date":"2021-02-14T11:15:43.721428Z","container_name":"fluentbit-dev-container","source":"stdout","log":"{\"time\":\"2021-02-14T11:15:43.72122498Z\",\"id\":\"\",\"remote_ip\":\"10.1.1.178\",\"host\":\"10.1.17.31\",\"method\":\"GET\",\"uri\":\"/\",\"user_agent\":\"ELB-HealthChecker/2.0\",\"status\":200,\"error\":\"\",\"latency\":39110,\"latency_human\":\"39.11\u00b5s\",\"bytes_in\":0,\"bytes_out\":18}","container_id":"af27785192944632a76e3d1696c42686-3097909514","ecs_cluster":"fluentbit-dev-cluster","ecs_task_arn":"arn:aws:ecs:us-east-1:[AccountID]:task/fluentbit-dev-cluster/af27785192944632a76e3d1696c42686","ecs_task_definition":"fluentbit-dev-task:19"} {"date":"2021-02-14T11:15:44.109974Z","source":"stdout","log":"{\"time\":\"2021-02-14T11:15:44.109820687Z\",\"id\":\"\",\"remote_ip\":\"10.1.2.37\",\"host\":\"10.1.17.31\",\"method\":\"GET\",\"uri\":\"/\",\"user_agent\":\"ELB-HealthChecker/2.0\",\"status\":200,\"error\":\"\",\"latency\":39215,\"latency_human\":\"39.215\u00b5s\",\"bytes_in\":0,\"bytes_out\":18}","container_id":"af27785192944632a76e3d1696c42686-3097909514","container_name":"fluentbit-dev-container","ecs_cluster":"fluentbit-dev-cluster","ecs_task_arn":"arn:aws:ecs:us-east-1:[AccountID]:task/fluentbit-dev-cluster/af27785192944632a76e3d1696c42686","ecs_task_definition":"fluentbit-dev-task:19"}
おわりに
FireLensはFluent BitまたはFluentdを起動するのに便利な機能であって、ログのルーティングはFluent Bit、Fluentdの設定次第ということがわかりました。設定ファイルはS3に置いておけるようになると自前イメージの管理の手間減って助かりますね。